﻿using System;
using System.Text;

using Sharkbite.Irc;

namespace mkv16
{
	/// <summary>
	/// High level connection protocol for sending and receiving messages on IRC.
	/// </summary>
    public class IRCProtocol : IProtocol
    {
		/// <summary>
		/// The connection is used to retrieve references to connection components.
		/// </summary>
	    private Connection connection;
        private ConnectionArgs cargs;
        private string id;
        private string nickname;

        /// <summary>
        /// These message events report useful information about ongoing IRC conversations.
        /// </summary>
        public event MessageEvent MessageReceived, MessageSendSuccess, MessageSendFail;
        /// <summary>
        /// These connection events report status of the IRC connection.
        /// </summary>
        public event ConnectionEvent ConnectionSuccess, ConnectionWarning, ConnectionFail;

        public void Connect(string service, string name)
        {
            id = "IRCProtocol:" + service;
            nickname = name;

            CreateConnection(service, name);
            RegisterEvents();
            start();
        }

        public void Disconnect()
        {
            connection.Disconnect("Bye");
        }

        public void Say(string domain, string recipient, string text)
        {
            var message = new Message(domain, cargs.Nick, text);

            try
            {
                if (connection.Connected && connection.Registered)
                {
                    if (domain == "private")
                        connection.Sender.PrivateMessage(recipient, text);
                    else
                        connection.Sender.PublicMessage(domain, text);

                    if (MessageSendSuccess != null)
                        MessageSendSuccess(this, message);
                }
                else
                {
                    if (ConnectionFail != null)
                        ConnectionFail(this, "Connection not connected or registered");
                    throw new Exception();
                }
            }
            catch(Exception)
            {
                if(MessageSendFail != null)
                    MessageSendFail(this, message);
            }
        }

        public void Join(string channel)
        {
            try
            {
                connection.Sender.Join(channel);

                if (ConnectionSuccess != null)
                    ConnectionSuccess(this, String.Format("Connection ready, joined {0}", channel));
            }
            catch (Exception e)
            {
                if (ConnectionFail != null)
                    ConnectionFail(this, String.Format("Could not join channel {0} reason {1}", channel, e.Message));
            }
        }

        /// <summary>
        /// Create a connection to the specified server
        /// </summary>
		protected void CreateConnection(string server, string nick) 
		{
			//Fire up the Ident server for those IRC networks
			//silly enough to use it.
			Identd.Start(nick);

			//A ConnectionArgs contains all the info we need to establish
			//our connection with the IRC server and register a user.
			//This line uses the simplfied contructor and the default values.
			//With this constructor the Nick, Real Name, and User name are
			//all set to the same value. It will use the default port of 6667 and no server
			//password.
			cargs = new ConnectionArgs(nick, server);
	
			//When creating a Connection two additional protocols may be
			//enabled: CTCP and DCC. In this example we will disable them
			//both.
			connection = new Connection( cargs, false, false );			

			//NOTE
			//We could have created multiple Connections to different IRC servers
			//and each would process messages simultaneously and independently.
			//There is no fixed limit on how many Connection can be opened at one time but
			//it is important to realize that each runs in its own Thread. Also,  separate event 
			//handlers are required for each connection, i.e. the
			//same OnRegistered() handler cannot be used for different connection
			//instances.
		}

        protected void RegisterEvents()
        {
            //OnRegister tells us that we have successfully established a connection with
            //the server. Once this is established we can join channels, check for people
            //online, or whatever.
            connection.Listener.OnRegistered += new RegisteredEventHandler(OnRegistered);

            //Listen for any messages sent to the channel
            connection.Listener.OnPublic += new PublicMessageEventHandler(OnPublic);

            //Listen for bot commands sent as private messages
            connection.Listener.OnPrivate += new PrivateMessageEventHandler(OnPrivate);

            //Listen for notification that an error has ocurred 
            connection.Listener.OnError += new ErrorMessageEventHandler(OnError);

            //Listen for notification that we are no longer connected.
            connection.Listener.OnDisconnected += new DisconnectedEventHandler(OnDisconnected);
        }

        protected void start() 
		{
			//Notice that by having the actual connect call here 
			//the constructor can add the necessary listeners before 
			//the connection process begins. If listeners are added
			//after connecting they may miss certain events. the OnRegistered()
			//event will certainly be missed.

			try
			{			
				//Calling Connect() will cause the Connection object to open a 
				//socket to the IRC server and to spawn its own thread. In this
				//separate thread it will listen for messages and send them to the 
				//Listener for processing.
				connection.Connect();

				//The main thread ends here but the Connection's thread is still alive.
				//We are now in a passive mode waiting for events to arrive.
			}
			catch( Exception e ) 
			{
                if(ConnectionFail != null)
                    ConnectionFail(this, String.Format("Error during connection process reason {0}", e.Message));
				Identd.Stop();
			}
		}

        protected void OnRegistered() 
		{
            string channel = "#" + nickname;

			//We have to catch errors in our delegates because Thresher purposefully
			//does not handle them for us. Exceptions will cause the library to exit if they are not
			//caught.
			try
			{ 
				//Don't need this anymore in this example but this can be left running
				//if you want.
				Identd.Stop();

                Join(channel);
			}
			catch( Exception e ) 
			{
                if (ConnectionFail != null)
                    ConnectionFail(this, String.Format("Error after connecting, tried joining channel {0} reason {1}", channel, e.Message));
			}
		}

        protected void OnPublic(UserInfo user, string channel, string message)
		{
            if (MessageReceived != null)
                MessageReceived(this, new Message(channel, user.Nick, message));
		}

        protected void OnPrivate(UserInfo user, string message)
		{
            if (MessageReceived != null)
                MessageReceived(this, new Message("private", user.Nick, message));
		}

        protected void OnError(ReplyCode code, string message) 
		{
			//All anticipated errors have a numeric code. The custom Thresher ones start at 1000 and
			//can be found in the ErrorCodes class. All the others are determined by the IRC spec
			//and can be found in RFC2812Codes.
            if (ConnectionWarning != null)
                ConnectionWarning(this, String.Format("An error of type {0} due to {1} has occurred.", code, message));
		}

        protected void OnDisconnected() 
		{
			//If this disconnection was involutary then you should have received an error
			//message ( from OnError() ) before this was called.
            if (ConnectionFail != null)
                ConnectionFail(this, "The connection to the server has been lost");
		}

        public string ID
        {
            get { return id; }
        }
	}
}